Skip to content

C# record perf + sentinel-based directive assembly#6755

Closed
jkschneider wants to merge 19 commits intomainfrom
jkschneider/cs-record-perf
Closed

C# record perf + sentinel-based directive assembly#6755
jkschneider wants to merge 19 commits intomainfrom
jkschneider/cs-record-perf

Conversation

@jkschneider
Copy link
Copy Markdown
Member

Summary

  • Replace C# record types with classes for LST node performance
  • Implement multi-parse preprocessor directives for C#
  • Replace fragile line-number-based directive interleaving with sentinel-based section assembly using ghost comments (//DIRECTIVE:N) and DirectiveBoundaryMarker

Test plan

  • All 68 CSharpRpcTest tests pass, including all 8 directive tests (parseSimpleIfEndif, parseSimpleIfElse, parseKeywordSplittingDirective, parseNestedDirectives, parseElifDirective, parseDirectiveWithComplexCondition, parseDirectiveChangingBaseClass, parsePolyfillConditionalClass)
  • Verify with a recipe that adds/removes lines within a directive branch

Implements C# parsing using Roslyn with round-trip printing:
- Core LST types: CompilationUnit, UsingDirective, PropertyDeclaration, AccessorDeclaration, AttributeList
- C#-specific expressions: NamedExpression, RefExpression, DeclarationExpression, CsLambda
- Pattern matching: RelationalPattern, PropertyPattern with JContainer<NamedExpression>
- Markers: DelegateInvocation, NullSafe (shared with Java), MultiDimensionalArray, NullCoalescing
- 265 passing tests covering statements, expressions, and pattern matching
- Add tests for arrays, async, casts, enums, interfaces, interpolated strings
- Add tests for locks, namespaces, records, structs, tuples, yield
- Update parser, printer, and visitor for improved C# support
Build the RPC layer that enables Java to communicate with the C# parser
over stdin/stdout using StreamJsonRpc. This includes:

- Visitor hierarchy: JavaVisitor<P> base class, CSharpVisitor<P> extends it
- RPC infrastructure in Rewrite.Core (RpcSendQueue, RpcObjectData, Reference)
- JavaSender for serializing J-type AST nodes
- CSharpSender with double-delegation pattern for Cs/J nodes
- RPC server (RewriteRpcServer) with Parse, GetObject, Print endpoints
- Java-side receiver, codec, and test infrastructure
- Fix JavaReceiver.visitPrimitive to consume keyword via onChange
- Improved error reporting in RpcReceiveQueue.receiveList
Resolve conflict in settings.gradle.kts by keeping both
rewrite-csharp and rewrite-docker in the project list.
Adds 13 new tree types for C# preprocessor directives as first-class LST
nodes: ConditionalBlock, IfDirective, ElifDirective, ElseDirective,
PragmaWarningDirective, NullableDirective, RegionDirective,
EndRegionDirective, DefineDirective, UndefDirective, ErrorDirective,
WarningDirective, and LineDirective.

Implements gap-scanning parser support in CSharpParser.cs that detects
directive lines in whitespace gaps between members/statements. Conditional
directives (#if/#elif/#else/#endif) are modeled but not yet parsed —
they remain as Space for now.

Full RPC round-trip support: visitor, printer, sender (C#), and receiver
(Java) for all 13 types. Six new round-trip tests verify parse-print
fidelity for #region, #pragma warning, #nullable, #warning, #line, and
multi-code #pragma warning directives.
- Add TreeVisitor<T, P> base class, ExecutionContext, Recipe, ScanningRecipe<T>, Result
- Add RecipeDescriptor, OptionDescriptor, and [Option] attribute with reflection-based extraction
- Add [LanguageInjection("markdown")] annotations via JetBrains.Annotations NuGet
- Add RewriteTest/RecipeSpec test infrastructure with end-to-end rename class test
- Remove CSharpRpcCodec and static codec registry in favor of constructor-injected tree codec
- Consolidate multi-project C# solution into single OpenRewrite project
…on, and expression-bodied method support

Delete Java-side CSharpPrinter and delegate all printing to C# via RPC,
matching the architecture used by JavaScript and Python. Add parser and
printer support for pattern matching (is-patterns with and/or/not,
declaration patterns via StatementExpression wrapper), switch expressions,
expression-bodied methods (ExpressionBodied marker on J.MethodDeclaration),
and preprocessor directives. Introduce C# marker types (PrimaryConstructor,
Implicit, Struct, RecordClass, ExpressionBodied) for syntactic features.
Implements the full recipe marketplace stack for C#:
- C# core model: CategoryDescriptor, RecipeMarketplace, IRecipeActivator
- RPC handlers: GetMarketplace, InstallRecipes, PrepareRecipe, Visit
- NuGet package download via dotnet CLI (mirrors JS npm install pattern)
- Java-side: NuGetRecipeBundleResolver/Reader, installRecipes client methods
- Transitive dependency loading from NuGet cache via project.assets.json
Add SearchResult and Markup (Error, Warn, Info, Debug) marker types
with RPC codec support. Implement MarkerPrinter interface with Default,
SearchMarkersOnly, Fenced, and Sanitized modes. Wire up BeforeSyntax
and AfterSyntax in CSharpPrinter to call marker printer hooks using
the /*~~(content)~~>*/ comment wrapper format, matching the pattern
used by rewrite-javascript and rewrite-python.
- Full JavaType model in C# with CSharpTypeMapping (Roslyn ISymbol → JavaType)
- Bidirectional RPC receiver (JavaReceiver.cs, CSharpReceiver.cs) for tree exchange
- RPC-based recipe execution: RpcVisitor delegates to Java peer via PrepareRecipe/Visit
- UsesType/UsesMethod preconditions with local fallback (Preconditions.cs, LocalUsesType.cs)
- 7 cross-language recipes registered for C# in recipes.csv
- 9 recipe tests: changeMethodName, findMethods, findTypes, deleteMethodArgument,
  reorderMethodArguments, changeType, changePackage, plus type attribution verification
- Fix CSharpParser.VisitType() to apply type attribution to user-defined types
Rename csharpProjectPath to csharpServerEntry to reflect that the entry
point can be either a .csproj file (launched via dotnet run --project) or
a published DLL (launched via dotnet <path>). This prepares for
CLI integration where the server is distributed as a published artifact.
- Remove RoslynRecipe and EmbeddedResourceHelper (dead code, replaced by RPC)
- Update target framework from net9.0 to net10.0 (net9.0 EOL Nov 2026)
- Add NuGet tool package publishing: csharpUpdateVersion, csharpPack,
  csharpPublish tasks in build.gradle.kts with snapshot versioning
- Make .csproj packable as dotnet tool (PackAsTool, OpenRewrite.CSharp)
Avoids modifying the .csproj file on every snapshot/release build,
keeping the working tree clean.
- Add ParseProject RPC handler for .csproj-based parsing with NuGet resolution
  and source-generator file discovery
- Add CSharpWhitespaceValidationService for detecting unparsed code in rewriteRun
- Add J.NullableType (int?, string?) with full J pipeline
- Add Cs.NullSafeExpression for null-forgiving operator (x!)
- Add VisitDestructorDeclaration mapping to J.MethodDeclaration (~ClassName)
- Add VisitRefExpression for standalone ref expressions (ref locals)
- Add Cs.InitializerExpression for object/collection initializers
- Add Cs.ExternAlias for extern alias directives
- Add Cs.PointerType and VisitFixedStatement support
- Add VisitLabeledStatement, VisitUnsafeStatement, VisitDefaultExpression,
  VisitSizeOfExpression, VisitLockStatement (as J.Synchronized)
- Register OmitParentheses marker for RPC serialization
- Simplify Cs.Keyword model in Java (remove unused enum values)
- 50 tests passing (36 RPC + 5 ParseProject + 9 Recipe)
…eParameter

Replace Cs.ClassDeclaration with J.ClassDeclaration for all C# type
declarations. C#-specific type parameter data (variance, attributes,
where-clause constraints) is now stored via a new ConstrainedTypeParameter
type in J.TypeParameter.Bounds[0].

Deleted types: CsClassDeclaration, ClassDeclarationKind, CsTypeParameter,
TypeParameterConstraintClause, TypeParameterBound, TypeConstraint,
AllowsConstraint interface, TypeParameterConstraint interface.

Constraint types (ClassOrStructConstraint, ConstructorConstraint,
DefaultConstraint, etc.) now implement Expression instead of the deleted
TypeParameterConstraint interface.
Map 21 additional Roslyn syntax types (delegates, events, indexers,
operators, goto, using statements, anonymous methods/objects, with
expressions, spread, function pointers, list/slice patterns, etc.),
add 20 new RPC round-trip tests, and remove 16 unused Cs model types
that the parser maps to J types instead.
Files with #if/#elif/#else/#endif are parsed once per unique symbol
permutation, each producing a complete CompilationUnit. A new
ConditionalDirective wraps all branches and a line-level interleaving
printer reconstructs the original source. Replaces the old
ConditionalBlock/IfDirective/ElifDirective/ElseDirective types which
could not handle directives splitting code at arbitrary points.
Records generate value equality checks that compare every field on
every visitor call, which is expensive for large ASTs. Switching to
classes with explicit With* methods and id-based equality (matching
the Java LST pattern) avoids this overhead.
… assembly

The previous line-number-based approach stored absolute line positions in
DirectiveLine.lineNumber and required all branches to have identical line
counts. If a recipe added or removed lines within a branch, the printer
produced corrupted output.

Ghost comments (//DIRECTIVE:N) are now emitted in clean source in place of
directive lines. These survive Roslyn parsing as whitespace text and act as
positional sentinels. A DirectiveBoundaryInjector scans the parsed tree and
attaches DirectiveBoundaryMarker to nodes adjacent to directive boundaries,
giving recipes structural access to boundary positions. The printer splits
each branch's output by the ghost comment pattern into sections, then
assembles sections from the appropriate branch — allowing different branches
to have different section sizes after recipe modifications.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

1 participant